package groovy.runtime.metaclass.aconcagua.measure

import aconcagua.measure.DividedUnit;
import aconcagua.measure.MultipliedUnit;
import aconcagua.measure.NullUnit;
import aconcagua.measure.SimpleUnit;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.MetaClass;

class DividedUnitMetaClass extends DelegatingMetaClass {
	
	DividedUnitMetaClass(MetaClass aMetaClass) {
		super(aMetaClass);
	}
	
	@Override
	public Object invokeConstructor(Object[] arguments) {
		return unitUsing(arguments[0], arguments[1]);
	}
	
	// (class) instance creation - private
	private unitUsing(DividedUnit aUnit, DividedUnit anotherUnit) {
		return (aUnit.numerator() * anotherUnit.denominator()) /
			(aUnit.denominator() * anotherUnit.numerator());
	}
	
	private unitUsing(DividedUnit aUnit, MultipliedUnit anotherUnit) {
		return aUnit.numerator() / (aUnit.denominator() * anotherUnit());
	}
	
	private unitUsing(DividedUnit aUnit, SimpleUnit anotherUnit) {
		return aUnit.numerator() / (aUnit.denominator() * anotherUnit);
	}
	
	private unitUsing(MultipliedUnit aUnit, DividedUnit anotherUnit) {
		return (aUnit * anotherUnit.denominator()) / anotherUnit.numerator();
	}
	
	private unitUsing(MultipliedUnit aUnit, MultipliedUnit anotherUnit) {
		def numeratorFactors = aUnit.factors();
		def denominatorFactors = [];

		anotherUnit.factors().each({ unit ->
			if (numeratorFactors.contains(unit))
				numeratorFactors.remove(unit)
			else denominatorFactors.add(unit) });
		
		def numerator = new MultipliedUnit(numeratorFactors);
		def denominator = new MultipliedUnit(denominatorFactors);

		return denominator == new NullUnit() ? numerator : super.invokeConstructor(numerator, denominator);
	}
	
	private unitUsing(MultipliedUnit aUnit, SimpleUnit anotherUnit) {
		def numeratorFactors = aUnit.factors();
		if (numeratorFactors.contains(anotherUnit)) {
			numeratorFactors.remove(anotherUnit);
			return new MultipliedUnit(numeratorFactors);
		} else
			return super.invokeConstructor(aUnit, anotherUnit);
	}
	
	private unitUsing(NullUnit aUnit, DividedUnit anotherUnit) {
		return super.invokeConstructor(anotherUnit.denominator(), anotherUnit.numerator());
	}
	
	private unitUsing(NullUnit aUnit, MultipliedUnit anotherUnit) {
		return super.invokeConstructor(aUnit, anotherUnit);
	}
	
	private unitUsing(NullUnit aUnit, SimpleUnit anotherUnit) {
		return super.invokeConstructor(aUnit, anotherUnit);
	}
	
	private unitUsing(SimpleUnit aUnit, DividedUnit anotherUnit) {
		return (aUnit * anotherUnit.denominator()) / anotherUnit.numerator();
	}
	
	private unitUsing(SimpleUnit aUnit, MultipliedUnit anotherUnit) {
		def denominatorFactors = anotherUnit.factors();
		if (denominatorFactors.constains(aUnit))
			denominatorFactors.remove(aUnit)
		else return new DividedUnit(aUnit, anotherUnit);
		
		// It never can be left NullUnit on NullUnit because aMultipliedUnit has
		// like minimum 2 units
		return super.invokeConstructor(new NullUnit(), new MultipliedUnit(denominatorFactors));
	}

	private unitUsing(SimpleUnit aUnit, SimpleUnit anotherUnit) {
		return (aUnit == anotherUnit) ? new NullUnit() : super.invokeConstructor(aUnit, anotherUnit);
	}
}
